Taro2.x 跨端开发实践
导读
随着业务的快速发展以及要拓展在线渠道至APP、小程序以及M站。与此同时,用户数量的上升需要优化前端的性能和用户体验。本文主要介绍业主业务如何利用Taro2.x实现跨端来进行业务的快速迭代。
背景
业主业务之前一直是以h5的形式嵌入安居客和58app中的,用户如果需要自己发布卖房信息或者委托经纪人,是只能在app中操作的。
为了达到以下三个目标:
提高用户体验
需要将发房渠道扩展到小程序和M站
节约成本和减少重复性劳动
我们尝试进行跨端开发(React Native、小程序和H5)。
对市面上主流小程序框架来进行了对比:
支持React Native、小程序和H5的只有Taro和uni-app
安居客小程序是以Taro开发的,提供了宿主环境给到各业务方使用,以及公司内部有MPS CLI(WuBa MiniProgram for Standardization,小程序全端跨平台开发解决方案)
考虑到时间成本以及技术体系,最终我们选定Taro来进行尝试多端开发。
Taro[2.x]的官方文档是这样写的:
使用 Taro,我们可以只书写一套代码,再通过 Taro 的编译工具,将源代码分别编译出可以在不同端(微信 / 京东 / 百度 / 支付宝 / 字节跳动 小程序、快应用、H5、React-Native 等)运行的代码。
看完文档以为跨端开发是这样的:
但,实际上是这样的:
虽然官方说了支持RN,但是对应的各端开发前注意-React Native是最多的,显然利用Taro编译Rn,需要的前置条件相比小程序和H5来说是比较多的。
我们在实践过程中,遇到了以下的问题:
官方提供的RN 0.59.9版本与安居客和58app不兼容
taro有些api在Rn端是依赖原生模块的,而这些模块在安居客58app没有集成
样式问题,RN对css的样式支持度很低
jsx语法限制,由于小程序的限制,有些写法可能在rn和h5端没有问题,但是小程序端会报错
RN端能力不足
Taro官方组件库TaroUI不支持RN
在解决问题之前,我们大概了解下Taro是如何将一套代码编译成可运行在多端的
Taro框架原理
Taro是一个编译时框架,采用的是编译原理的思想,是对输入的源代码进行语法分析,语法树构建,随后对语法树进行转换操作再解析生成目标代码的过程。
Taro1.x编译构建系统是taro自研的:
在2.x时,则是将各端编译器剥离,CLI只做区分编译平台、处理不同平台编译入参等操作,随后再调用对应平台的 runner 编译器 做代码编译操作,而原来大量的 AST 语法操作改造成 Webpack Plugin 以及 Loader,交给 Webpack 来处理。
Taro2.x:
解决问题
1. 官方提供的RN 0.59.9版本,安居客和58app不支持
这个问题只能修改Taro自身的RN版本,修改成安居客和58app支持的对应版本
2. taro有些api在Rn端是依赖原生模块的,而这些模块在安居客58app没有集成
RN开发时,如果你用了依赖原生模块的方法,而这个模块原生没有集成,那么就会出现红屏警告。
针对上面两个问题我们选择了私有化Taro Cli来进行解决:
我们是基于Taro2.2.6版本开发的,为了使Taro兼容58RN工程我们对源码做了以下改造:
@tarojs/cli修改react-native版本
@tarojs/helper、@tarojs/taro-rn、@tarojs/rn-runner修改cli依赖,删除安居客、58不支持的原生依赖(expo)
修改后上传到了58npm,然后在项目的.npmrc中添加对应的registry
上面两个问题解决后,就可以进行RN的开发了:
整个 RN 端的开发流程如下:
执行yarn dev:rn,Taro会先编译成RN代码放在rn_temp下,再通过metro server启动服务打包rn_temp下的js文件生成jsbundle,就可以在app通过127.0.0.1:8081访问rn页面。
那么这里来解决第三个问题:3. 样式问题,RN对css的样式支持度很低
这是官方的开发注意说明:
样式上 H5 最为灵活,小程序次之,RN 最弱,统一多端样式即是对齐短板,也就是要以 RN 的约束来管理样式,同时兼顾小程序的限制,核心可以用三点来概括:
使用 Flex 布局
基于 BEM 写样式
采用 style 属性覆盖组件样式
RN 中 View 标签默认主轴方向是 column,如果不将其他端改成与 RN 一致,就需要在所有用到 display: flex 的地方都显式声明主轴方向。
那么这里怎么将RN的样式区分出来呢?
Taro是有提供样式的条件编译:
但实际使用时,2.2.6 样式文件条件编译*.rn.scss不起作用,文件内条件编译是可以的,但是编译时会有警告。
因为2.x里用的都是webpack,这里直接用webpack.NormalModuleReplacementPlugin来进行修改,将RN的样式剥离出来,这样编写样式时就能以RN为基准来进行编写。
webpackChain(chain, webpack) {
chain.merge({
plugin: {
NormalModuleReplacementPlugin: {
plugin: new webpack.NormalModuleReplacementPlugin(
/\.scss/,
function (resource) {
let isReplace = true;
for (let i = 0; i < BUILD_TYPE.length; i++) {
const ext = BUILD_TYPE[i];
if (resource.request.indexOf(`.${ext}.scss`) > -1) {
isReplace = false;
break;
}
}
const replaceStr = resource.request.replace(
/\.scss/,
`.rn.scss`
);
if (
fs.existsSync(resource.context + '/' + replaceStr) &&
isReplace
) {
resource.request = replaceStr;
}
}
),
},
},
});
},
在index.scss中直接@import ./index.rn.scss,然后补充小程序和h5支持的如fixed等css属性。
还有一个问题是,Taro处理样式文件时,每个样式文件都会有这样一个函数:
// 一般app 只有竖屏模式,所以可以只获取一次 width
const deviceWidthDp = Dimensions.get('window').width
const uiWidthPx = 375
function scalePx2dp (uiElementPx) {
return uiElementPx * deviceWidthDp / uiWidthPx
}
}
然后将10px替换成scalePx2dp(10),这样就会发现在iPhone 6 和6Plus上的字体大小是不一致的,会根据屏幕来进行等比缩放的
但在业主项目中,UI规范是要求字体大小、宽高不根据屏幕进行缩放,h5不要使用rem,所以需要通过修改postcss-pxtransform、rn-runner 和Taro.pxTransform
怎么样修改node_modules中的代码呢,一般是私有化,但也有很方便的方法。
我们可以直接使用patch-package修改node_modules中包的源码
然后在package.json中添加:
"scripts": {
+ "postinstall": "patch-package"
}
其中在rn中是将className处理成style的,覆盖组件样式可以通过style传递,但style可能是数组,这点需要注意下。
4. jsx语法限制,由于小程序的限制,有些写法可能在rn和h5端没有问题,但是小程序端会报错
由于小程序的限制,有些写法可能在rn和h5端没有问题,但是小程序端会报错,所以使用Taro编写代码时需要遵循Taro的最佳实践。
5. RN端能力不足
6. Taro官方组件库TaroUI不支持RN
Taro提供的组件和Api相对比较基础,通用性更强,但基本都是以小程序提供的api为基准,适配到h5和rn,在rn端是利用Expo进行支持的,但是58和安居客app端并没有集成对应的依赖,所以需要利用app端已有的功能进行封装,抹平业务中的平台判断。
例如rn中的请求Request是必需的API,利用原生Module提供的Request进行支持:
新增rn-request.ts:
import { NativeModules } from 'react-native';
declare type RequestData = {
method: string;
url: string;
params: any;
};
export function fetch(requestData: RequestData): Promise<any> | null {
return new Promise<any>((resolve, reject) =>
NativeModules.HTTPModule.Request(
requestData,
(response: any) => {
resolve(response);
},
(error: string) => {
reject(error);
}
)
);
}
在fetch.ts中:
export default async function fetch(options: RequestParams) {
const {
url,
data = {},
method = 'GET',
header = {},
credentials = 'include'
} = options;
try {
let result;
if (process.env.TARO_ENV === 'rn') {
result = await require('./rn-request').fetch({
url,
method,
params: data || {},
});
} else {
result = await Taro.request({
url,
method,
data,
header,
credentials,
});
}
...
// do somethine
...
业务中使用到的业务组件如picker、modal等,不同端的Api如登录、跳转等需要我们自行封装。
使用条件编译来抹平不同端的差异:
if (process.env.TARO_ENV === 'weapp') {
// 微信小程序端执行逻辑
} else if (process.env.TARO_ENV === 'h5') {
// h5 端执行逻辑
} else if (process.env.TARO_ENV === 'rn') {
// react-native 端执行逻辑
}
发布:
Rn是利用58的rn热更新平台进行打包,所以需要将rn_temp下的文件推送到特定分支,比如rn_deploy,然后在热更新平台配置仓库地址、分支,进行构建ios和安卓包。
小程序则是作为分包接入安居客微信小程序中,利用58的小程序全端跨平台开发解决方案MPS CLI发布。
H5的还是走云平台部署流程。
最终实现的效果:
总结与展望
我们利用Taro开发了新版房贷计算器、业主频道、业主直卖表单和管理详情页,以及正在开发的业主房源管理页等。
达到了我们的目标:
提高用户体验
需要将发房渠道扩展到小程序和M站
节约成本和减少重复性劳动
但在实践中,踩了不少坑,在开发业务的同时需要针对api和组件进行底层封装、Taro框架调整,以及样式适配ios、安卓、小程序和h5,实际业务开发时间比开发H5相同需求多了1倍左右。
目前跨端开发还处于摸石头过河的阶段,多端统一的组件、sdk积累较少,在组件库、sdk丰富完善后,可以抹平各端差异,开发效率基本接近目前小程序、h5开发情况。
目前在和app同学一起补全RN的端能力,推动app、小程序的底层api和组件的统一,实现业务开发无需关注各端差异,专注于业务本身,高效开发。理想情况下,使用Taro编写的业务代码可以无缝对接到多端。
作者简介:
https://taro-docs.jd.com/taro/docs/2.x/README
https://aotu.io/notes/2020/01/08/taro-2-0/
https://aotu.io/notes/2018/06/07/Taro/
福利环节
为了鼓励优质内容传播,【58技术】公众号近期会持续推出不定期活动奖励。
评论区互动留言,即可参与此次活动
留言转发集赞,点赞量前三名(点赞数需大于10)可获得定制版新年代码台历一本
活动时间:截至2021年1月15日